Skip to content

Worker MultiStreamConsumer#1726

Closed
ibc wants to merge 10 commits into
v3from
multi-stream-consumer
Closed

Worker MultiStreamConsumer#1726
ibc wants to merge 10 commits into
v3from
multi-stream-consumer

Conversation

@ibc
Copy link
Copy Markdown
Member

@ibc ibc commented Feb 9, 2026

Details

  • MultiStreamConsumer replaces SimpleConsumer and SimulcastConsumer and merges both.

Things to test / double-check

  • TestMultiStreamConsumer fails miserably. AFAIS no packet is finally sent, probably due to some checks about current spatial layers and so on.
  • this->encodingContext can now be nullptr. Check it.
  • Must check this->keyFrameSupported, so also those if (!packet->IsKeyFrame()) conditions?
  • Must test with ALAW or whatever non-OPUS codec.

# Details

- `MultiStreamConsumer` replaces `SimpleConsumer` and `SimulcastConsumer` and merges both.

## TODO

I've renamed `TestSimpleConsumer.cpp` to `TestMultiStreamConsumer.cpp` and it fails miserably. And I don't understand why.

Run it as follows:

```bash
MEDIASOUP_TEST_TAGS="[consumer]" MS_TEST_LOG_LEVEL=debug MS_TEST_LOG_TAGS="rtp simulcast" make test
```

Output:

```
RTC::MultiStreamConsumer::MultiStreamConsumer() | ------ encoding.ssrc:1234567890
RTC::MultiStreamConsumer::CreateRtpStream() | [ssrc:1234567890, payloadType:111]
RTC::MultiStreamConsumer::SendRtpPacket() | ---- 5 packet->GetSsrc():5, mapMappedSsrcSpatialLayer.size():1
RTC::MultiStreamConsumer::SendRtpPacket() | ------ 5.1 <this->mapMappedSsrcSpatialLayer>
RTC::MultiStreamConsumer::SendRtpPacket() | ------------ mappeSsrc:1234567890, spatialLayer:0
RTC::MultiStreamConsumer::SendRtpPacket() | ------ 5.1 </this->mapMappedSsrcSpatialLayer>

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
mediasoup-worker-test is a Catch2 v3.12.0 host application.
Run with -? for options

-------------------------------------------------------------------------------
Scenario: MultiStreamConsumer
  RTP packets are not forwarded when the consumer is not active
-------------------------------------------------------------------------------
../../../test/src/RTC/TestMultiStreamConsumer.cpp:232
...............................................................................

../../../test/src/RTC/TestMultiStreamConsumer.cpp:232: FAILED:
  {Unknown expression after the reported line}
due to unexpected exception with message:
  absl::container_internal::raw_hash_map<>::at
```

Pay attention to these temporal logs I added in `SendRtpPacket()`:

```c++
	void MultiStreamConsumer::SendRtpPacket(RTC::RTP::Packet* packet, RTC::RTP::SharedPacket& sharedPacket)
	{
		MS_TRACE();

#ifdef MS_RTC_LOGGER_RTP
		packet->logger.consumerId = this->id;
#endif

		// TODO
		MS_DUMP(
		  "---- 5 packet->GetSsrc():%" PRIu32 ", mapMappedSsrcSpatialLayer.size():%zu",
		  packet->GetSsrc(),
		  this->mapMappedSsrcSpatialLayer.size());

		// TODO
		MS_DUMP("------ 5.1 <this->mapMappedSsrcSpatialLayer>");
		for (const auto& kv : this->mapMappedSsrcSpatialLayer)
		{
			MS_DUMP("------------ mappeSsrc:%" PRIu32 ", spatialLayer:%" PRIi16, kv.first, kv.second);
		}
		MS_DUMP("------ 5.1 </this->mapMappedSsrcSpatialLayer>");

		auto spatialLayer = this->mapMappedSsrcSpatialLayer.at(packet->GetSsrc());

		// TODO
		MS_DUMP("----- 5.2 OK!!!");
```

How the hell is possible that it crashes in `auto spatialLayer = this->mapMappedSsrcSpatialLayer.at(packet->GetSsrc())` line if it's clear that an entry with key (`mappedSsrc: 1234567890`) is present in `this->mapMappedSsrcSpatialLayer`?.
@ibc ibc requested a review from jmillan February 9, 2026 17:28
@ibc ibc marked this pull request as ready for review February 10, 2026 14:49
@ibc ibc marked this pull request as draft February 10, 2026 14:52
@versatica versatica deleted a comment from github-actions Bot Feb 10, 2026
@versatica versatica deleted a comment from github-actions Bot Feb 10, 2026
@versatica versatica deleted a comment from github-actions Bot Feb 10, 2026
@versatica versatica deleted a comment from github-actions Bot Feb 10, 2026
@ibc
Copy link
Copy Markdown
Member Author

ibc commented Feb 10, 2026

TODO 1

STR

  1. npm run local in mediasoup-demo/app.
  2. Edit the producer tab and add &mic=false and &webcam=false (optionally also &enableWebcamLayers=false to reduce complexity in logs). Reload the tabs.
  3. Then create mic Producer via CC.enableMic(). Audio is properly received in the receiver tab.
  4. Enable webcam in the sender. Audio is still good in the receiver tab.
  5. Now in the DevTools console of the sender tab call CC.disableMic(), then CC.enableMic(). No audio packet will be sent to the consumer.
CleanShot 2026-02-10 at 15 59 17

@jmillan
Copy link
Copy Markdown
Member

jmillan commented Feb 10, 2026

if mic and webcam are enabled then eventually remote audio Consumer stats show bitrate 0, and indeed there is no audio from mediasoup to the receiver.

Build mediasoup with MESON_ARGS="-Dms_rtc_logger_rtp=true" and you'll see the reason for every dropped packet.

@ibc
Copy link
Copy Markdown
Member Author

ibc commented Feb 10, 2026

if mic and webcam are enabled then eventually remote audio Consumer stats show bitrate 0, and indeed there is no audio from mediasoup to the receiver.

Build mediasoup with MESON_ARGS="-Dms_rtc_logger_rtp=true" and you'll see the reason for every dropped packet.

Actually no rtc logger rtp related logs ONCE audio consumer is "lost".

@ibc
Copy link
Copy Markdown
Member Author

ibc commented Feb 10, 2026

More about TODO 1: Audio consumer issue when there is video as well

(Read STR in updated comment TODO 1 above: #1726 (comment))

When the sender only enables audio, the audio Consumer sends all audio packets properly to the consuming browser tab and those are its logs. Everything good AFAIS (I think):

RTC::MultiStreamConsumer::GetProducerCurrentRtpStream() | ----- this->currentSpatialLayer == -1 => nullptr
RTC::MultiStreamConsumer::GetProducerCurrentRtpStream() | ----- this->currentSpatialLayer == -1 => nullptr
RTC::MultiStreamConsumer::SendRtpPacket() | ***** BEGIN audio packet | seq:15817
RTC::MultiStreamConsumer::SendRtpPacket() | ----- !IsActive() => audio packet ignored because spatialLayer 0 != this->currentSpatialLayer -1 | seq:15817
RTC::MultiStreamConsumer::MayChangeLayers() | ----- calling UpdateTargetLayers(0, 0)
RTC::MultiStreamConsumer::UpdateTargetLayers() | ***** UpdateTargetLayers(0, 0)
RTC::MultiStreamConsumer::UpdateTargetLayers() | ----- setting this->targetLayers.spatial = 0, this->targetLayers.temporal = 0
RTC::MultiStreamConsumer::SendRtpPacket() | ***** BEGIN audio packet | seq:15818
RTC::MultiStreamConsumer::SendRtpPacket() | ----- unsupported codec => audio packet ignored because spatialLayer 0 != this->currentSpatialLayer -1 | seq:15818
RTC::MultiStreamConsumer::SendRtpPacket() | ***** BEGIN audio packet | seq:15819
RTC::MultiStreamConsumer::SendRtpPacket() | ----- unsupported codec => audio packet ignored because spatialLayer 0 != this->currentSpatialLayer -1 | seq:15819
RTC::MultiStreamConsumer::SendRtpPacket() | ***** BEGIN audio packet | seq:15820
RTC::MultiStreamConsumer::SendRtpPacket() | ----- unsupported codec => audio packet ignored because spatialLayer 0 != this->currentSpatialLayer -1 | seq:15820
RTC::MultiStreamConsumer::SendRtpPacket() | ***** BEGIN audio packet | seq:15821
RTC::MultiStreamConsumer::SendRtpPacket() | ----- unsupported codec => audio packet ignored because spatialLayer 0 != this->currentSpatialLayer -1 | seq:15821
RTC::MultiStreamConsumer::SendRtpPacket() | ***** BEGIN audio packet | seq:15822
RTC::MultiStreamConsumer::SendRtpPacket() | ----- unsupported codec => audio packet ignored because spatialLayer 0 != this->currentSpatialLayer -1 | seq:15822
RTC::MultiStreamConsumer::SendRtpPacket() | ***** BEGIN audio packet | seq:15823
RTC::MultiStreamConsumer::SendRtpPacket() | ----- unsupported codec => audio packet ignored because spatialLayer 0 != this->currentSpatialLayer -1 | seq:15823
RTC::MultiStreamConsumer::SendRtpPacket() | ***** BEGIN audio packet | seq:15824
RTC::MultiStreamConsumer::SendRtpPacket() | ----- unsupported codec => audio packet ignored because spatialLayer 0 != this->currentSpatialLayer -1 | seq:15824
RTC::MultiStreamConsumer::SendRtpPacket() | ***** BEGIN audio packet | seq:15825
RTC::MultiStreamConsumer::SendRtpPacket() | ----- unsupported codec => audio packet ignored because spatialLayer 0 != this->currentSpatialLayer -1 | seq:15825
RTC::MultiStreamConsumer::SendRtpPacket() | ***** BEGIN audio packet | seq:15826
RTC::MultiStreamConsumer::SendRtpPacket() | ----- unsupported codec => audio packet ignored because spatialLayer 0 != this->currentSpatialLayer -1 | seq:15826
RTC::MultiStreamConsumer::SendRtpPacket() | ***** BEGIN audio packet | seq:15827
RTC::MultiStreamConsumer::SendRtpPacket() | ----- unsupported codec => audio packet ignored because spatialLayer 0 != this->currentSpatialLayer -1 | seq:15827
RTC::MultiStreamConsumer::SendRtpPacket() | ***** BEGIN audio packet | seq:15828
RTC::MultiStreamConsumer::SendRtpPacket() | ----- unsupported codec => audio packet ignored because spatialLayer 0 != this->currentSpatialLayer -1 | seq:15828
RTC::MultiStreamConsumer::SendRtpPacket() | ***** BEGIN audio packet | seq:15829
RTC::MultiStreamConsumer::SendRtpPacket() | ----- setting this->currentSpatialLayer = 0 [previous value:-1] | seq:15829
RTC::MultiStreamConsumer::SendRtpPacket() | +++++ AUDIO PACKET SENT :) | seq:15829
RTC::MultiStreamConsumer::SendRtpPacket() | ***** BEGIN audio packet | seq:15830
RTC::MultiStreamConsumer::SendRtpPacket() | ----- audio packet dropped EMPTY_PAYLOAD | seq:15830
RTC::MultiStreamConsumer::SendRtpPacket() | ***** BEGIN audio packet | seq:15831
RTC::MultiStreamConsumer::SendRtpPacket() | ----- audio packet dropped EMPTY_PAYLOAD | seq:15831
RTC::MultiStreamConsumer::SendRtpPacket() | ***** BEGIN audio packet | seq:15832
RTC::MultiStreamConsumer::SendRtpPacket() | ----- audio packet dropped EMPTY_PAYLOAD | seq:15832
RTC::MultiStreamConsumer::SendRtpPacket() | ***** BEGIN audio packet | seq:15833
RTC::MultiStreamConsumer::SendRtpPacket() | ----- audio packet dropped EMPTY_PAYLOAD | seq:15833
RTC::MultiStreamConsumer::SendRtpPacket() | ***** BEGIN audio packet | seq:15834
RTC::MultiStreamConsumer::SendRtpPacket() | ----- audio packet dropped EMPTY_PAYLOAD | seq:15834
RTC::MultiStreamConsumer::SendRtpPacket() | ***** BEGIN audio packet | seq:15835
RTC::MultiStreamConsumer::SendRtpPacket() | ----- audio packet dropped EMPTY_PAYLOAD | seq:15835
RTC::MultiStreamConsumer::SendRtpPacket() | ***** BEGIN audio packet | seq:15836
RTC::MultiStreamConsumer::SendRtpPacket() | ----- audio packet dropped EMPTY_PAYLOAD | seq:15836
RTC::MultiStreamConsumer::SendRtpPacket() | ***** BEGIN audio packet | seq:15837
RTC::MultiStreamConsumer::SendRtpPacket() | ----- audio packet dropped EMPTY_PAYLOAD | seq:15837
RTC::MultiStreamConsumer::SendRtpPacket() | ***** BEGIN audio packet | seq:15838
RTC::MultiStreamConsumer::SendRtpPacket() | ----- audio packet dropped EMPTY_PAYLOAD | seq:15838
RTC::MultiStreamConsumer::SendRtpPacket() | ***** BEGIN audio packet | seq:15839
RTC::MultiStreamConsumer::SendRtpPacket() | +++++ AUDIO PACKET SENT :) | seq:15839
RTC::MultiStreamConsumer::SendRtpPacket() | ***** BEGIN audio packet | seq:15840
RTC::MultiStreamConsumer::SendRtpPacket() | +++++ AUDIO PACKET SENT :) | seq:15840
RTC::MultiStreamConsumer::SendRtpPacket() | ***** BEGIN audio packet | seq:15841
RTC::MultiStreamConsumer::SendRtpPacket() | +++++ AUDIO PACKET SENT :) | seq:15841
RTC::MultiStreamConsumer::SendRtpPacket() | ***** BEGIN audio packet | seq:15842
RTC::MultiStreamConsumer::SendRtpPacket() | +++++ AUDIO PACKET SENT :) | seq:15842
RTC::MultiStreamConsumer::SendRtpPacket() | ***** BEGIN audio packet | seq:15843
RTC::MultiStreamConsumer::SendRtpPacket() | +++++ AUDIO PACKET SENT :) | seq:15843
RTC::MultiStreamConsumer::SendRtpPacket() | ***** BEGIN audio packet | seq:15844
RTC::MultiStreamConsumer::SendRtpPacket() | +++++ AUDIO PACKET SENT :) | seq:15844
RTC::MultiStreamConsumer::SendRtpPacket() | ***** BEGIN audio packet | seq:15845
RTC::MultiStreamConsumer::SendRtpPacket() | +++++ AUDIO PACKET SENT :) | seq:15845
RTC::MultiStreamConsumer::SendRtpPacket() | ***** BEGIN audio packet | seq:15846
RTC::MultiStreamConsumer::SendRtpPacket() | +++++ AUDIO PACKET SENT :) | seq:15846
RTC::MultiStreamConsumer::SendRtpPacket() | ***** BEGIN audio packet | seq:15847
RTC::MultiStreamConsumer::SendRtpPacket() | +++++ AUDIO PACKET SENT :) | seq:15847
RTC::MultiStreamConsumer::SendRtpPacket() | ***** BEGIN audio packet | seq:15848
RTC::MultiStreamConsumer::SendRtpPacket() | +++++ AUDIO PACKET SENT :) | seq:15848
RTC::MultiStreamConsumer::SendRtpPacket() | ***** BEGIN audio packet | seq:15849
RTC::MultiStreamConsumer::SendRtpPacket() | +++++ AUDIO PACKET SENT :) | seq:15849
RTC::MultiStreamConsumer::SendRtpPacket() | ***** BEGIN audio packet | seq:15850
RTC::MultiStreamConsumer::SendRtpPacket() | +++++ AUDIO PACKET SENT :) | seq:15850
RTC::MultiStreamConsumer::SendRtpPacket() | ***** BEGIN audio packet | seq:15851
RTC::MultiStreamConsumer::SendRtpPacket() | +++++ AUDIO PACKET SENT :) | seq:15851
RTC::MultiStreamConsumer::SendRtpPacket() | ***** BEGIN audio packet | seq:15852
RTC::MultiStreamConsumer::SendRtpPacket() | +++++ AUDIO PACKET SENT :) | seq:15852
RTC::MultiStreamConsumer::SendRtpPacket() | ***** BEGIN audio packet | seq:15853
RTC::MultiStreamConsumer::SendRtpPacket() | +++++ AUDIO PACKET SENT :) | seq:15853
RTC::MultiStreamConsumer::SendRtpPacket() | ***** BEGIN audio packet | seq:15854
RTC::MultiStreamConsumer::SendRtpPacket() | +++++ AUDIO PACKET SENT :) | seq:15854
RTC::MultiStreamConsumer::SendRtpPacket() | ***** BEGIN audio packet | seq:15855
RTC::MultiStreamConsumer::SendRtpPacket() | +++++ AUDIO PACKET SENT :) | seq:15855
RTC::MultiStreamConsumer::SendRtpPacket() | ***** BEGIN audio packet | seq:15856
RTC::MultiStreamConsumer::SendRtpPacket() | +++++ AUDIO PACKET SENT :) | seq:15856
RTC::MultiStreamConsumer::SendRtpPacket() | ***** BEGIN audio packet | seq:15857
RTC::MultiStreamConsumer::SendRtpPacket() | +++++ AUDIO PACKET SENT :) | seq:15857
RTC::MultiStreamConsumer::SendRtpPacket() | ***** BEGIN audio packet | seq:15858
RTC::MultiStreamConsumer::SendRtpPacket() | ----- audio packet dropped DROPPED_BY_CODEC | seq:15858
RTC::MultiStreamConsumer::SendRtpPacket() | ***** BEGIN audio packet | seq:15859
RTC::MultiStreamConsumer::SendRtpPacket() | +++++ AUDIO PACKET SENT :) | seq:15859
RTC::MultiStreamConsumer::SendRtpPacket() | ***** BEGIN audio packet | seq:15860
RTC::MultiStreamConsumer::SendRtpPacket() | +++++ AUDIO PACKET SENT :) | seq:15860
RTC::MultiStreamConsumer::SendRtpPacket() | ***** BEGIN audio packet | seq:15861
RTC::MultiStreamConsumer::SendRtpPacket() | +++++ AUDIO PACKET SENT :) | seq:15861
RTC::MultiStreamConsumer::SendRtpPacket() | ***** BEGIN audio packet | seq:15862
RTC::MultiStreamConsumer::SendRtpPacket() | +++++ AUDIO PACKET SENT :) | seq:15862
RTC::MultiStreamConsumer::SendRtpPacket() | ***** BEGIN audio packet | seq:15863
RTC::MultiStreamConsumer::SendRtpPacket() | +++++ AUDIO PACKET SENT :) | seq:15863

When pausing mic Producer:

RTC::MultiStreamConsumer::UserOnPaused() | ----- calling UpdateTargetLayers(-1, -1) [current this->currentSpatialLayer:0]
RTC::MultiStreamConsumer::UpdateTargetLayers() | ***** UpdateTargetLayers(-1, -1)
RTC::MultiStreamConsumer::UpdateTargetLayers() | ----- newTargetSpatialLayer == -1 => setting this->targetLayers.spatial = -1, this->targetLayers.temporal = -1, this->currentSpatialLayer = -1

When resuming mic Producer:

RTC::MultiStreamConsumer::GetProducerCurrentRtpStream() | ----- this->currentSpatialLayer == -1 => nullptr
RTC::MultiStreamConsumer::MayChangeLayers() | ----- calling UpdateTargetLayers(0, 0)
RTC::MultiStreamConsumer::UpdateTargetLayers() | ***** UpdateTargetLayers(0, 0)
RTC::MultiStreamConsumer::UpdateTargetLayers() | ----- setting this->targetLayers.spatial = 0, this->targetLayers.temporal = 0
RTC::MultiStreamConsumer::SendRtpPacket() | ***** BEGIN audio packet | seq:20111
RTC::MultiStreamConsumer::SendRtpPacket() | ----- setting this->currentSpatialLayer = 0 [previous value:-1] | seq:20111
RTC::MultiStreamConsumer::SendRtpPacket() | +++++ AUDIO PACKET SENT :) | seq:20111
RTC::MultiStreamConsumer::SendRtpPacket() | ***** BEGIN audio packet | seq:20112
RTC::MultiStreamConsumer::SendRtpPacket() | +++++ AUDIO PACKET SENT :) | seq:20112
RTC::MultiStreamConsumer::SendRtpPacket() | ***** BEGIN audio packet | seq:20113
RTC::MultiStreamConsumer::SendRtpPacket() | +++++ AUDIO PACKET SENT :) | seq:20113

Now I add webcam Producer in the sender tab (logs related to video are NOT shown, so those logs are just about the existing audio Consumer). BTW nothing to show here since everything works fine, nothing changed

Now, with webcam Producer still enabled, I close the mic Producer (CC.disableMic()) and create another one (CC.enableMic()), and from now on ALL AUDIO PACKETS are dropped because of this:

RTC::MultiStreamConsumer::GetProducerCurrentRtpStream() | ----- this->currentSpatialLayer == -1 => nullptr
RTC::MultiStreamConsumer::GetProducerCurrentRtpStream() | ----- this->currentSpatialLayer == -1 => nullptr
RTC::MultiStreamConsumer::SendRtpPacket() | ***** BEGIN audio packet | seq:31404
RTC::MultiStreamConsumer::SendRtpPacket() | ----- this->targetLayers.temporal == -1 => audio packet ignored because spatialLayer 0 != this->currentSpatialLayer -1 | seq:31404
RTC::MultiStreamConsumer::SendRtpPacket() | ***** BEGIN audio packet | seq:31405
RTC::MultiStreamConsumer::SendRtpPacket() | ----- this->targetLayers.temporal == -1 => audio packet ignored because spatialLayer 0 != this->currentSpatialLayer -1 | seq:31405
RTC::MultiStreamConsumer::SendRtpPacket() | ***** BEGIN audio packet | seq:31406
RTC::MultiStreamConsumer::SendRtpPacket() | ----- this->targetLayers.temporal == -1 => audio packet ignored because spatialLayer 0 != this->currentSpatialLayer -1 | seq:31406
RTC::MultiStreamConsumer::SendRtpPacket() | ***** BEGIN audio packet | seq:31407
RTC::MultiStreamConsumer::SendRtpPacket() | ----- this->targetLayers.temporal == -1 => audio packet ignored because spatialLayer 0 != this->currentSpatialLayer -1 | seq:31407
RTC::MultiStreamConsumer::SendRtpPacket() | ***** BEGIN audio packet | seq:31408
RTC::MultiStreamConsumer::SendRtpPacket() | ----- this->targetLayers.temporal == -1 => audio packet ignored because spatialLayer 0 != this->currentSpatialLayer -1 | seq:31408
RTC::MultiStreamConsumer::SendRtpPacket() | ***** BEGIN audio packet | seq:31409
RTC::MultiStreamConsumer::SendRtpPacket() | ----- this->targetLayers.temporal == -1 => audio packet ignored because spatialLayer 0 != this->currentSpatialLayer -1 | seq:31409
RTC::MultiStreamConsumer::SendRtpPacket() | ***** BEGIN audio packet | seq:31410
RTC::MultiStreamConsumer::SendRtpPacket() | ----- this->targetLayers.temporal == -1 => audio packet ignored because spatialLayer 0 != this->currentSpatialLayer -1 | seq:31410
RTC::MultiStreamConsumer::SendRtpPacket() | ***** BEGIN audio packet | seq:31411
RTC::MultiStreamConsumer::SendRtpPacket() | ----- this->targetLayers.temporal == -1 => audio packet ignored because spatialLayer 0 != this->currentSpatialLayer -1 | seq:31411
RTC::MultiStreamConsumer::SendRtpPacket() | ***** BEGIN audio packet | seq:31412
RTC::MultiStreamConsumer::SendRtpPacket() | ----- this->targetLayers.temporal == -1 => audio packet ignored because spatialLayer 0 != this->currentSpatialLayer -1 | seq:31412
RTC::MultiStreamConsumer::SendRtpPacket() | ***** BEGIN audio packet | seq:31413
RTC::MultiStreamConsumer::SendRtpPacket() | ----- this->targetLayers.temporal == -1 => audio packet ignored because spatialLayer 0 != this->currentSpatialLayer -1 | seq:31413
RTC::MultiStreamConsumer::SendRtpPacket() | ***** BEGIN audio packet | seq:31414
RTC::MultiStreamConsumer::SendRtpPacket() | ----- this->targetLayers.temporal == -1 => audio packet ignored because spatialLayer 0 != this->currentSpatialLayer -1 | seq:31414

Basically, when there is a video Consumer in the same transport, audio packets are ignored in the audio Consumer. And this happens specially if the video Producer is created first of if the video RTP arrives to mediasoup before the first audio packet (I think).

@versatica versatica deleted a comment from github-actions Bot Feb 11, 2026
@versatica versatica deleted a comment from github-actions Bot Feb 11, 2026
@versatica versatica deleted a comment from github-actions Bot Feb 11, 2026
@github-actions
Copy link
Copy Markdown

clang-tidy review says "All clean, LGTM! 👍"

@ibc
Copy link
Copy Markdown
Member Author

ibc commented Feb 11, 2026

To simplify: the problem is that when there is a video Consumer in same transport and later an audio Consumer is created, this->targetLayers.temporal is ALWAYS -1 in the audio Consumer and never moves to 0.

Probably we run something that changes this->targetLayers if the audio Consumer is the FIRST CONSUMER in the Transport, but that doesn't happen if there were already another Consumer. Maybe this is related to Consumer::TransportConnected().

@github-actions
Copy link
Copy Markdown

clang-tidy review says "All clean, LGTM! 👍"

@ibc
Copy link
Copy Markdown
Member Author

ibc commented Feb 11, 2026

I think I found the problem:

Working audio

There is only audio Consumer:

RTC::MultiStreamConsumer::GetProducerCurrentRtpStream() | ----- this->currentSpatialLayer == -1 => nullptr
RTC::MultiStreamConsumer::UserOnTransportConnected() | ***** UserOnTransportConnected()
RTC::MultiStreamConsumer::GetProducerCurrentRtpStream() | ----- this->currentSpatialLayer == -1 => nullptr
RTC::MultiStreamConsumer::MayChangeLayers() | ***** MayChangeLayers(force:false)
RTC::MultiStreamConsumer::MayChangeLayers() | ----- calling UpdateTargetLayers(0, 0)
RTC::MultiStreamConsumer::UpdateTargetLayers() | ***** UpdateTargetLayers(0, 0)
RTC::MultiStreamConsumer::UpdateTargetLayers() | ----- setting this->targetLayers.spatial = 0, this->targetLayers.temporal = 0
RTC::MultiStreamConsumer::SendRtpPacket() | ***** BEGIN audio packet | seq:14568
RTC::MultiStreamConsumer::SendRtpPacket() | ---- this->targetLayers:
            <ConsumerTypes::VideoLayers>
              spatial:0, temporal:0
            </ConsumerTypes::VideoLayers>
RTC::MultiStreamConsumer::SendRtpPacket() | ---- this->provisionalTargetLayers:
            <ConsumerTypes::VideoLayers>
              spatial:-1, temporal:-1
            </ConsumerTypes::VideoLayers>
RTC::MultiStreamConsumer::SendRtpPacket() | ---- this->targetLayers:
            <ConsumerTypes::VideoLayers>
              spatial:0, temporal:0
            </ConsumerTypes::VideoLayers>
RTC::MultiStreamConsumer::SendRtpPacket() | ----- setting this->currentSpatialLayer = 0 [previous value:-1] | seq:14568
RTC::MultiStreamConsumer::SendRtpPacket() | +++++ AUDIO PACKET SENT :) | seq:14568

Notice that UserOnTransportConnected() unconditionally calls (if IsActive() which is true) to MayChangeLayers() and that ends calling UpdateTargetLayers(0, 0) (GOOD).

Failing audio

There is a video Consumer running. Then an audio Consumer is created and it fails:

RTC::MultiStreamConsumer::GetProducerCurrentRtpStream() | ----- this->currentSpatialLayer == -1 => nullptr
RTC::MultiStreamConsumer::UserOnTransportConnected() | ***** UserOnTransportConnected()
RTC::MultiStreamConsumer::GetProducerCurrentRtpStream() | ----- this->currentSpatialLayer == -1 => nullptr
RTC::MultiStreamConsumer::MayChangeLayers() | ***** MayChangeLayers(force:false)
RTC::MultiStreamConsumer::MayChangeLayers() | ----- this->externallyManagedBitrate == true
RTC::MultiStreamConsumer::MayChangeLayers() | ----- this->externallyManagedBitrate == true => calling listener->OnConsumerNeedBitrateChange()
RTC::MultiStreamConsumer::SendRtpPacket() | ***** BEGIN audio packet | seq:17253
RTC::MultiStreamConsumer::SendRtpPacket() | ---- this->targetLayers:
            <ConsumerTypes::VideoLayers>
              spatial:-1, temporal:-1
            </ConsumerTypes::VideoLayers>
RTC::MultiStreamConsumer::SendRtpPacket() | ---- this->provisionalTargetLayers:
            <ConsumerTypes::VideoLayers>
              spatial:-1, temporal:-1
            </ConsumerTypes::VideoLayers>
RTC::MultiStreamConsumer::SendRtpPacket() | ---- this->targetLayers:
            <ConsumerTypes::VideoLayers>
              spatial:-1, temporal:-1
            </ConsumerTypes::VideoLayers>
RTC::MultiStreamConsumer::SendRtpPacket() | ----- this->targetLayers.temporal == -1 => audio packet ignored because spatialLayer 0 != this->currentSpatialLayer -1 | seq:17253

Notice that UserOnTransportConnected() is also called (expected) for the new audio Consumer, that's not the problem. The problem is that now in MayChangeLayers() method this->externallyManagedBitrate is true so instead of invoking UpdateTargetLayers() it invokes listener->OnConsumerNeedBitrateChange() with the hope that the listener (Transport) will play the BWE game and assign bandwidth to the audio Consumer and so on, but MultiStreamConsumer::GetDesiredBitrate() returns 0 if this->kind == AUDIO so the audio Consumer is excluded from the BWE game and hence UpdateTargetLayers() is never invoked.

@ibc
Copy link
Copy Markdown
Member Author

ibc commented Feb 11, 2026

Audio Consumer issue fixed here: 212a2ee

@github-actions
Copy link
Copy Markdown

clang-tidy review says "All clean, LGTM! 👍"

@ibc
Copy link
Copy Markdown
Member Author

ibc commented Feb 12, 2026

I will leave this draft PR open temporary until we have a proper approach so then will close it.

@ibc
Copy link
Copy Markdown
Member Author

ibc commented Feb 24, 2026

Closing in favour of PR #1731.

@ibc ibc closed this Feb 24, 2026
@ibc ibc deleted the multi-stream-consumer branch May 26, 2026 12:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants